#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

#include "main.h"
#include "keywordMatch.h"
#include "mnemonicHandlers.h"
#include "labelHandlers.h"

//const char * testprogram = ".data byte letter 0 byte inside 1 .code mov R1 [R3 + @inside]  EOF ";
//int pIndex = 0;

int main(int argc, char * argv[])
{
    FILE * pFile = fopen(argv[1], "r");
    if (pFile == 0) {
        printf ("file not found\n");
        return 0;
    }

    FILE * pFileOut = fopen(argv[2], "w");
    char error[MAX_STRING_LEN];
    compile (error, pFile, pFileOut);
    fclose(pFile);
    fclose(pFileOut);
    return 0;
}

int compile (char * errorMsg, FILE * pFile, FILE * pFileOut) {
    struct program prog;
    prog.currentByte = 0;
    struct labelManager lManager;
    char writeBuffer[MAX_STRING_LEN];
    int segmentType;
    while (true)
    {
        getNextString (writeBuffer, pFile);
        segmentType = matchSegment (writeBuffer);
        if(segmentType == SEG_DATA) {
            parseLabels (errorMsg, pFile, &lManager);
            break;
        }
    }
    //pIndex = 0;
    fseek (pFile, 0, SEEK_SET);
    int codeLen;
    while (true)
    {
        getNextString (writeBuffer, pFile);
        segmentType = matchSegment (writeBuffer);
        if(segmentType == SEG_CODE) {
            codeLen = addressLabels(errorMsg, pFile, &lManager);
            break;
        }
    }

    //pIndex = 0;
    fseek (pFile, 0, SEEK_SET);
    while (true)
    {
        getNextString (writeBuffer, pFile);
        segmentType = matchSegment (writeBuffer);
        if(segmentType == SEG_DATA) {
            assignLabels (errorMsg, pFile, &prog, &lManager);
            break;
        }
    }
    //pIndex = 0;
    fseek (pFile, 0, SEEK_SET);
    while (true)
    {
        getNextString (writeBuffer, pFile);
        segmentType = matchSegment (writeBuffer);
        if(segmentType == SEG_CODE) {
            parseCode (errorMsg, pFile, &prog, &lManager);
            break;
        }
    }

    int i;
    for(i = 0; i < codeLen; i++)
    {
        printf("%d ", prog.bytes[i]);
        fprintf(pFileOut,"%d ", prog.bytes[i]);
    }
    printf("\n");

    return 0;
}

int parseCode (char * errorMsg, FILE * pFile, struct program * prog, struct labelManager * lManager)
{
    char writeBuffer[MAX_STRING_LEN];
    int mnemonic;
    while(getNextString (writeBuffer, pFile) != 0)
    {
        mnemonic = matchMnemonic (writeBuffer);
        if (mnemonic == MN_HALT) {
            addHaltCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_ADD) {
            addAddCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_SUB) {
            addSubCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_MUL) {
            addMulCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_INC) {
            addIncCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_DEC) {
            addDecCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_BEEP) {
            addBeepCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_PRINT) {
            addPrintCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_PRINTA) {
            addPrintaCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_PRINTI) {
            addPrintiCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_MOV) {
            addMovCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_JMP) {
            addJmpCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_JZ) {
            addJzCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_JNZ) {
            addJnzCode (errorMsg, pFile, writeBuffer, lManager, prog);
        } else if (mnemonic == MN_CLEAR) {
            addClearCode (errorMsg, pFile, writeBuffer, lManager, prog);
        }
    }
    return 0;
}

int assignLabels (char * errorMsg, FILE * pFile, struct program * prog, struct labelManager * lManager) {
    char writeBuffer[MAX_STRING_LEN];
    int labelType;
    int segType = -1;
    while (segType != SEG_CODE) {
        getNextString (writeBuffer, pFile);
        segType = matchSegment (writeBuffer);
        labelType = matchLabelType (writeBuffer);
        if (labelType == LABEL_BYTE) {
            assignByteLabel (errorMsg, pFile, writeBuffer, lManager);
        } else if (labelType == LABEL_ARRAY) {
            assignArrayLabel (errorMsg, pFile, writeBuffer, lManager);
        }
    }

    int nLabels = lManager->currentLabel;
    int i;
    for (i = 0; i < nLabels; i++) {
        if (lManager->labels[i].type == LABEL_BYTE || lManager->labels[i].type == LABEL_IMMEDIATE_LABEL) {
            prog->bytes[lManager->labels[i].address] = lManager->labels[i].value;
        } else if (lManager->labels[i].type == LABEL_ARRAY) {
            int arrayOffset;
            for (arrayOffset = 0; arrayOffset < lManager->labels[i].arrayLen; arrayOffset++) {
                prog->bytes[lManager->labels[i].address + arrayOffset] = lManager->labels[i].array[arrayOffset];
            }
        }
    }
    return 0;
}

int addressLabels (char * errorMsg, FILE * pFile, struct labelManager * lManager) {
    int codeLen = 0;
    int mnemonic;
    char writeBuffer[MAX_STRING_LEN];
    while (getNextString (writeBuffer, pFile) != 0) {
        mnemonic = matchMnemonic (writeBuffer);
        if (mnemonic == MN_HALT || mnemonic == MN_BEEP) {
            codeLen++;
        } else if (mnemonic == MN_INC || mnemonic == MN_DEC || mnemonic == MN_ADD
                || mnemonic == MN_SUB || mnemonic == MN_MUL || mnemonic == MN_CLEAR)
        {
            codeLen++;
            getNextString (writeBuffer, pFile);
        } else if (mnemonic == MN_PRINT || mnemonic == MN_PRINTA || mnemonic == MN_PRINTI
                || mnemonic == MN_JMP || mnemonic == MN_JZ  || mnemonic == MN_JNZ )
        {
            codeLen++;
            getNextString (writeBuffer, pFile);
            if (isDataAddress (writeBuffer)) {
                int nLabel = findLabelByName (lManager, (char*)(writeBuffer + 1));
                lManager->labels[nLabel].address = codeLen;
            }
            codeLen++;
        } else if (mnemonic == MN_MOV) {
            codeLen++;
            getNextString (writeBuffer, pFile);
            if (isDataAddress (writeBuffer)) {
                int nLabel = findLabelByName (lManager, (char*)(writeBuffer + 1));
                lManager->labels[nLabel].address = codeLen;
            } else if (isBracket (writeBuffer)) {
                int mRegister;
                int mSign;
                char temp[MAX_STRING_LEN];
                parseBracket (writeBuffer, &mRegister, &mSign, temp);
                if (isDataAddress (temp)) {
                    int nLabel = findLabelByName (lManager, (char*)(temp + 1));
                    lManager->labels[nLabel].address = codeLen;
                }
            }
            getNextString (writeBuffer, pFile);
            if (isDataAddress (writeBuffer)) {
                int nLabel = findLabelByName (lManager, (char*)(writeBuffer + 1));
                lManager->labels[nLabel].address = codeLen;
            } else if (isBracket (writeBuffer)) {
                int mRegister;
                int mSign;
                char temp[MAX_STRING_LEN];
                parseBracket (writeBuffer, &mRegister, &mSign, temp);
                if (isDataAddress (temp)) {
                    int nLabel = findLabelByName (lManager, (char*)(temp + 1));
                    lManager->labels[nLabel].address = codeLen;
                }
            }
            codeLen ++;
        } else if (matchLabelType(writeBuffer) == LABEL_LABEL) {
            addLabelLabel (errorMsg, pFile, writeBuffer, lManager, codeLen);
        }
    }
    //printf("%d\n", codeLen);
    int nLabels = lManager->currentLabel;
    int i = 0;
    for (i = 0; i < nLabels; i++) {
        //printf("%d %d %d %d %d %s\n", LABEL_BYTE, LABEL_IMMEDIATE_LABEL, LABEL_ARRAY, codeLen, lManager->labels[i].type, lManager->labels[i].name);
        if (lManager->labels[i].type == LABEL_BYTE || lManager->labels[i].type == LABEL_IMMEDIATE_LABEL) {
            if (lManager->labels[i].address == -1) {
                lManager->labels[i].address = codeLen;
                codeLen++;
            }
        } else if (lManager->labels[i].type == LABEL_ARRAY) {
            if (lManager->labels[i].address == -1) {
                lManager->labels[i].address = codeLen;
                codeLen += lManager->labels[i].arrayLen;
            }
        }
    }
    addSpecialLabelLabel (errorMsg, pFile, "programEnd" , lManager, codeLen);
    return codeLen;
}

int parseLabels (char * errorMsg, FILE * pFile, struct labelManager * lManager) {
    char writeBuffer[MAX_STRING_LEN];
    int labelType;
    while(getNextString(writeBuffer, pFile) != 0) {
        labelType = matchLabelType(writeBuffer);
        if (labelType == LABEL_BYTE) {
            addByteLabel (errorMsg, pFile, writeBuffer, lManager);
        } else if (labelType == LABEL_ARRAY) {
            addArrayLabel (errorMsg, pFile, writeBuffer, lManager);
        } else {
            int mnemonic = matchMnemonic (writeBuffer);
            if (mnemonic == MN_MOV) {
                getNextString (writeBuffer, pFile); //only second operand allowed to be immediate
                addImmediateLabel (errorMsg, pFile, writeBuffer, lManager);
            } else if (mnemonic == MN_PRINTI) {
                addImmediateLabel (errorMsg, pFile, writeBuffer, lManager);
            }
        }
    }
    return 0;
}


int getNextString(char * writeTo, FILE * pFile)
{
    int length = 0;
    int charType;
    char curChar;
    bool foundString = false;
    bool commentMode = false;
    bool bracketMode = false;
    bool asciiMode = false;
    while(fread (&curChar, 1, 1, pFile) != 0) {

        //curChar = testprogram[pIndex];
        //pIndex++;
        charType = validCharacter(curChar);

        if (commentMode || asciiMode || bracketMode) {
            if (commentMode) {
                if(charType == NEWLINE_CHAR ) {
                    commentMode = false;
                }
            } else if (asciiMode) {
                if(charType == NEWLINE_CHAR) {
                    //error
                    return 0;
                } else {
                    writeTo[length] = curChar;
                    length++;
                    if (charType == APOSTROPHE_CHAR) {
                        break;
                    }
                }
            } else if (bracketMode) {
                if(charType == NEWLINE_CHAR) {
                    //error
                    return 0;
                } else {
                    writeTo[length] = curChar;
                    length++;
                    if (charType == END_BRACKET_CHAR) {
                        break;
                    }
                }
            }
        } else {
            if (charType == COMMENT_CHAR) {
                commentMode = true;
            } else if (charType == START_BRACKET_CHAR) {
                if (foundString == true)
                {
                    //error
                    return 0;
                } else {
                    bracketMode = true;
                    writeTo[length] = curChar;
                    length++;
                }
            } else if (charType == APOSTROPHE_CHAR) {
                if (foundString == true)
                {
                    //error
                    return 0;
                } else {
                    asciiMode = true;
                    writeTo[length] = curChar;
                    length++;
                }
            } else if(charType == WHITESPACE) {
                if(!commentMode) {
                    if(foundString) {
                        break;
                    }
                }
            } else if(charType == NEWLINE_CHAR) {
                if(commentMode) {
                    commentMode = false;
                }
                if(foundString) {
                    break;
                }
            } else if(charType == VALID_CHAR) {
                if(!commentMode) {
                    foundString = true;
                    writeTo[length] = curChar;
                    length++;
                }
            } else if (charType == INVALID_CHAR) {
                if(!commentMode) {
                    return -1;
                }
            }
        }
    }
    writeTo[length] = 0;
    if(!strcmp(writeTo, "EOF"))
    {
        return 0;
    }
    return length;
}

int validCharacter(char testChar)
{
    if(testChar == ASCII_HTAB || testChar == ASCII_SPACE) {
        return WHITESPACE;
    } else if (testChar == ASCII_NEWLINE) {
        return NEWLINE_CHAR;
    } else if (testChar == ASCII_SCOLON) {
        return COMMENT_CHAR;
    } else if (testChar == ASCII_APOSTROPHE) {
        return APOSTROPHE_CHAR;
    } else if (testChar == ASCII_LSBRACKET) {
        return START_BRACKET_CHAR;
    } else if (testChar == ASCII_RSBRACKET) {
        return END_BRACKET_CHAR;
    } else if (testChar == ASCII_AT
            || testChar == ASCII_COLON
            || testChar == ASCII_UNDERSCORE
            || testChar == ASCII_POINT
            || (ASCII_ZERO <= testChar && testChar <= ASCII_NINE)
            || (ASCII_U_A  <= testChar && testChar <= ASCII_U_Z )
            || (ASCII_L_A  <= testChar && testChar <= ASCII_L_Z ) )
    {
        return VALID_CHAR;
    } else {
        return INVALID_CHAR;
    }
}



void addProgramByte (struct program * prog, int byte) {
    prog->bytes[prog->currentByte] = byte;
    prog->currentByte++;
}

void addLabel (char * errorMsg, struct labelManager * lManager, int type, int address,
               int value, int arrayLen, int * array, char * name)
{
    int lPos = lManager->currentLabel;
    if (type == LABEL_IMMEDIATE_LABEL) {
        if (findImmediateLabelByValue (lManager, value) == -1)
        {
            lManager->labels[lPos].type = type;
            lManager->labels[lPos].value = value;
            lManager->labels[lPos].address = address;
            lManager->currentLabel++;
        }
    } else if (findLabelByName (lManager, name) == -1) {
        if (type == LABEL_BYTE) {
            lManager->labels[lPos].type = type;
            lManager->labels[lPos].value = value;
            lManager->labels[lPos].address = address;
            lManager->labels[lPos].name = name;
            lManager->currentLabel++;
        } else if (type == LABEL_ARRAY) {
            lManager->labels[lPos].type = type;
            lManager->labels[lPos].array = array;
            lManager->labels[lPos].arrayLen = arrayLen;
            lManager->labels[lPos].address = address;
            lManager->labels[lPos].name = name;
            lManager->currentLabel++;
        } else if (type == LABEL_LABEL) {
            lManager->labels[lPos].type = type;
            lManager->labels[lPos].value = value;
            lManager->labels[lPos].name = name;
            lManager->currentLabel++;
        }
    } else {
        //error, double declaration
    }
}


